
#include "autoconfig.h"
#include "qe_log.h"
#include "qe_spi.h"
#include "qe_device.h"
#include "qe_spi_flash.h"
#include "qe_memory.h"
#include "qe_assert.h"
#include "qe_macros.h"



QELOG_DOMAIN(QELOG_DOMAIN_SPIF);



/* flash capacity define */
#define SIZE_KB(n)			(n * 1024)
#define SIZE_MB(n)			(n * 1024 * 1024)



struct qe_spif_device {

	struct qe_device 		 parent;
	struct qe_spi_device    *spidev;
	
	qe_spif_chip chip;

	qe_u32 num_sectors;				/* sector number */
	qe_u32 sector_size;				/* bytes per sector */
	qe_u32 block_size;				/* bytes per erase block */

	qe_u32 address_4byte:1;
	qe_u32 reserve:31;
};

/* manufacturer information */
typedef struct{
    const char *name;
    qe_u8 id;
}spif_mf;

#if defined(CONFIG_SPI_FLASH_TABLE)
static qe_spif_chip match_table[] = {
{"AT45DB161E", 	JEDEC_ID_ATMEL,   0x26, 0x00, 0x81, SIZE_MB(2),   QE_SPIF_WM_BYTE_DBF,  0x0, 0x0, 512},
{"W25Q40BV", 	JEDEC_ID_WINBOND, 0x40, 0x13, 0x20, SIZE_KB(512), QE_SPIF_WM_PAGE_256B, 0x0, 0x0, 4096},
{"W25Q16BV", 	JEDEC_ID_WINBOND, 0x40, 0x15, 0x20, SIZE_MB(2),   QE_SPIF_WM_PAGE_256B, 0x0, 0x0, 4096},
{"W25Q64CV", 	JEDEC_ID_WINBOND, 0x40, 0x17, 0x20, SIZE_MB(8),   QE_SPIF_WM_PAGE_256B, 0x0, 0x0, 4096},
{"W25Q64DW", 	JEDEC_ID_WINBOND, 0x60, 0x17, 0x20, SIZE_MB(8),   QE_SPIF_WM_PAGE_256B, 0x0, 0x0, 4096},
{"W25Q128BV", 	JEDEC_ID_WINBOND, 0x40, 0x18, 0x20, SIZE_MB(16),  QE_SPIF_WM_PAGE_256B, 0x0, 0x0, 4096},
{"W25Q256FV", 	JEDEC_ID_WINBOND, 0x40, 0x19, 0x20, SIZE_MB(32),  QE_SPIF_WM_PAGE_256B, 0x0, 0x0, 4096},
{"W25QM512JV",	JEDEC_ID_WINBOND, 0x71, 0x19, 0x20, SIZE_MB(16),  QE_SPIF_WM_PAGE_256B, 0x0, 0x0, 4096},
{"SST25VF016B", JEDEC_ID_SST, 	  0x25, 0x41, 0x20, SIZE_MB(2),   QE_SPIF_WM_BYTE_AAI,  0x0, 0x0, 4096},
{"M25P32", 		JEDEC_ID_MICRON,  0x20, 0x16, 0xD8, SIZE_MB(4),   QE_SPIF_WM_PAGE_256B, 0x0, 0x0, SIZE_KB(64)},
{"M25P80", 		JEDEC_ID_MICRON,  0x20, 0x14, 0xD8, SIZE_MB(1),   QE_SPIF_WM_PAGE_256B, 0x0, 0x0, SIZE_KB(64)},
{"M25P40", 		JEDEC_ID_MICRON,  0x20, 0x13, 0xD8, SIZE_KB(512), QE_SPIF_WM_PAGE_256B, 0x0, 0x0, SIZE_KB(64)},
{"MT25QL256", 	JEDEC_ID_MICRON,  0xBA, 0x19, 0x20, SIZE_MB(32),  QE_SPIF_WM_PAGE_256B, 0x0, 0x0, 4096},
{"EN25Q32B", 	JEDEC_ID_EON, 	  0x30, 0x16, 0x20, SIZE_MB(4),   QE_SPIF_WM_PAGE_256B, 0x0, 0x0, 4096},
{"GD25Q64B", 	JEDEC_ID_GIGADEV, 0x40, 0x17, 0x20, SIZE_MB(8),   QE_SPIF_WM_PAGE_256B, 0x0, 0x0, 4096},
{"GD25Q16B", 	JEDEC_ID_GIGADEV, 0x40, 0x15, 0x20, SIZE_MB(2),   QE_SPIF_WM_PAGE_256B, 0x0, 0x0, 4096},
{"S25FL216K", 	JEDEC_ID_CYPRESS, 0x40, 0x15, 0x20, SIZE_MB(2),   QE_SPIF_WM_PAGE_256B, 0x0, 0x0, 4096},
{"S25FL032P", 	JEDEC_ID_CYPRESS, 0x02, 0x15, 0x20, SIZE_MB(4),   QE_SPIF_WM_PAGE_256B, 0x0, 0x0, 4096},
{"A25L080", 	JEDEC_ID_AMIC, 	  0x30, 0x14, 0x20, SIZE_MB(1),   QE_SPIF_WM_PAGE_256B, 0x0, 0x0, 4096},
{"F25L004", 	JEDEC_ID_ESMT, 	  0x20, 0x13, 0x20, SIZE_KB(512), QE_SPIF_WM_BYTE_AAI,  0x0, 0x0, 4096},
{"PCT25VF016B", JEDEC_ID_SST, 	  0x25, 0x41, 0x20, SIZE_MB(2),   QE_SPIF_WM_BYTE_AAI,  0x0, 0x0, 4096},
};
#endif

static spif_mf mf_table[] = {
    {"Cypress",    JEDEC_ID_CYPRESS},
    {"Fujitsu",    JEDEC_ID_FUJITSU},
    {"EON",        JEDEC_ID_EON},
    {"Atmel",      JEDEC_ID_ATMEL},
    {"Micron",     JEDEC_ID_MICRON},
    {"AMIC",       JEDEC_ID_AMIC},
    {"Sanyo",      JEDEC_ID_SANYO},
    {"Intel",      JEDEC_ID_INTEL},
    {"ESMT",       JEDEC_ID_ESMT},
    {"Fudan",      JEDEC_ID_FUDAN},
    {"Hyundai",    JEDEC_ID_HYUNDAI},
    {"SST",        JEDEC_ID_SST},
    {"GigaDevice", JEDEC_ID_GIGADEV},
    {"ISSI",       JEDEC_ID_ISSI},
    {"Winbond",    JEDEC_ID_WINBOND},
    {"Macronix",   JEDEC_ID_MACRONIX},
};

static qe_const_str spif_manufactor_str(qe_u8 id)
{
	switch (id) {
	case JEDEC_ID_CYPRESS: return "Cypress";
	case JEDEC_ID_FUJITSU: return "Fujitsu";
	case JEDEC_ID_EON: return "EON";
	case JEDEC_ID_ATMEL: return "Atmel";
	case JEDEC_ID_MICRON: return "Micron";
	case JEDEC_ID_AMIC: return "AMIC";
	case JEDEC_ID_SANYO: return "Sanyo";
	case JEDEC_ID_INTEL: return "Intel";
	case JEDEC_ID_ESMT: return "ESMT";
	case JEDEC_ID_FUDAN: return "Fudan";
	case JEDEC_ID_HYUNDAI: return "Hyundai";
	case JEDEC_ID_SST: return "SST";
	case JEDEC_ID_GIGADEV: return "GigaDevice";
	case JEDEC_ID_ISSI: return "ISSI";
	case JEDEC_ID_WINBOND: return "Winbond";
	case JEDEC_ID_MACRONIX: return "Macronix";
	default: return "Unknown";
	}
}

static void make_address_byte_array(qe_spif_dev *flash, qe_u32 addr, qe_u8 *array)
{
    qe_u8 len, i;

    len = flash->address_4byte ? 4 : 3;

    for (i=0; i<len; i++) {
        array[i] = (addr >> ((len - (i + 1)) * 8)) & 0xFF;
    }
}

static qe_ret read_jedec_id(qe_spif_dev *flash)
{
	qe_int n;
	qe_u8 cmd[1], data[3];

	cmd[0] = QE_SPIF_CMD_JEDEC_ID;
	qe_memset(data, 0, sizeof(data));
	n = qe_spi_send_recv(flash->spidev, cmd, 1, data, 3);
	if (n < 0) {
		qe_debug("read JEDEC ID error:%d", n);
		return qe_err_read;
	}

	qe_hexdump_debug(data, 3);

	flash->chip.mf_id = data[0];
	flash->chip.mt_id = data[1];
	flash->chip.capacity_id = data[2];

	qe_debug("Manufacturer ID: 0x%2X", flash->chip.mf_id);
	qe_debug("Memory type ID: 0x%2X",  flash->chip.mt_id);
	qe_debug("Capacity ID: 0x%2X",     flash->chip.capacity_id);

	return qe_ok;
}

static qe_ret read_status(qe_spif_dev *flash, qe_u8 *status)
{
	qe_int n;
	qe_u8 cmd = QE_SPIF_CMD_READ_SR;
	n = qe_spi_send_recv(flash->spidev, &cmd, 1, status, 1);
    //qe_debug("status:%d", status);
	if (n < 0) {
		return qe_err_send;
	}

	return qe_ok;
}

static qe_ret wait_busy(qe_spif_dev *dev)
{
	qe_ret ret;
	qe_u8 status = 0x0;

    //qe_debug("wait busy");

	while (1) {
		ret = read_status(dev, &status);
        //qe_debug("status:0x%x ret:%d", status, ret);
		if ((ret==qe_ok) && !(status & QE_SPIF_SR_BUSY)) {
			break;
		}
	}

	if ((ret!=qe_ok) || (status & QE_SPIF_SR_BUSY)) {
		qe_error("wait busy");
	}

    //qe_debug("wait busy ok");

	return ret;
}

static qe_ret write_enable(qe_spif_dev *dev, qe_bool en)
{
	qe_size n;
	qe_ret ret = qe_ok;
	qe_u8 cmd, sr;

	qe_assert(dev != QE_NULL);

	if (en) {
		cmd = QE_SPIF_CMD_WRITE_ENABLE;
	} else {
		cmd = QE_SPIF_CMD_WRITE_DISABLE;
	}

	n = qe_spi_transfer(dev->spidev, &cmd, QE_NULL, 1);
	if (n) {
		ret = read_status(dev, &sr);
	}

	if (ret == qe_ok) {
		if (en && !(sr & QE_SPIF_SR_WEL)) {
			qe_error("write enable status error");
			return qe_err_write;
		} else if (!en && (sr & QE_SPIF_SR_WEL)) {
			return qe_err_write;
		}
	}

	return ret;
}

static qe_ret spif_reset(qe_spif_dev *dev)
{
	qe_size n;
	qe_u8 cmd[1];

	cmd[0] = QE_SPIF_CMD_ENABLE_RESET;
	n = qe_spi_transfer(dev->spidev, cmd, QE_NULL, 1);
	if (n <= 0) {
		qe_error("enable reset failed");
		return qe_err_send;
	}

	wait_busy(dev);

	cmd[0] = QE_SPIF_CMD_RESET;
	n = qe_spi_transfer(dev->spidev, cmd, QE_NULL, 1);
	if (n <= 0) {
		qe_error("reset failed");
		return qe_err_send;
	}

	wait_busy(dev);

	return qe_ok;
}

/**
 * This function will write flash data for write 1 to 256 bytes per page
 * 
 * @flash      : the flash device
 * @addr  	   : write start address
 * @size       : write size
 * @write_gran : write granularity bytes, ony 1 or 256
 *
 * @return write length
 */
qe_ret page256_write(qe_spif_dev *dev, qe_u32 addr, qe_size size, qe_u16 write_gran, const qe_u8 *buffer)
{
	qe_size n;
	qe_ret ret = qe_ok;
	static qe_u8 cmd[5 + 256];
	qe_u8 cmd_size;
	qe_size write_size;

	qe_assert(dev);
	qe_assert((write_gran==1) || (write_gran==256));

	/* check flash address bound */
	if ((addr+size) > dev->chip.capacity) {
		qe_error("out of bound, write:0x%x-0x%x capacity:0x%x",
			addr, addr+size, dev->chip.capacity);
		return qe_err_range;
	}

	/* loop write operate. write unit is write granularity */
	while (size) {

		/* write enable */		
		ret = write_enable(dev, qe_true);
		if (ret != qe_ok) {
			goto __exit;
		}

		cmd[0] = QE_SPIF_CMD_PAGE_PROGRAM;
		make_address_byte_array(dev, addr, &cmd[1]);
		cmd_size = dev->address_4byte ? 5 : 4;

		/* make write align and calculate next write address */
		if (addr % write_gran != 0) {
			if (size > write_gran - (addr % write_gran)) {
				write_size = write_gran - (addr % write_gran);
			} else {
				write_size = size;
			}
		} else {
			if (size > write_gran) {
            	write_size = write_gran;
        	} else {
            	write_size = size;
        	}
		}

		size -= write_size;
        addr += write_size;

		qe_memcpy(&cmd[cmd_size], (void *)buffer, write_size);
		n = qe_spi_transfer(dev->spidev, cmd, QE_NULL, cmd_size+write_size);
		if (n == 0) {
			qe_error("write spi error");
			goto __exit;
		}

		/* wait busy */
		ret = wait_busy(dev);
		if (ret != qe_ok) {
			qe_error("wait busy");
			goto __exit;
		}

		buffer += write_size;
	}

__exit:
	write_enable(dev, qe_false);
	return ret;
}

/**
 * This function will write flash data for auto address increment mode
 *
 * @note: If address is odd number, it will place on 0xFF before the start of data for protect
 *        the old data. If the latest remain size is 1, it will append one 0xFF at the end of 
 *        data for protect the old data.
 *
 * @flash  : the flash device
 * @addr   : write start address
 * @size   : write size
 * @data   : write data
 *
 * @return result
 */
static qe_ret aai_write(qe_spif_dev *flash, qe_u32 addr, qe_size size, const qe_u8 *buffer)
{
	qe_size n;
	qe_ret ret;
	qe_u8 cmd[8], cmd_size;
	qe_bool first_write = qe_true;

	qe_assert(flash);
	qe_assert(flash->spidev);

	/* check flash address bound */
	if ((addr+size) > flash->chip.capacity) {
		qe_error("address out of bound");
		return qe_err_range;
	}

	/**
	 * The address must be even for AAI write mode. So it must write 
     * one byte first when address is odd. 
     */
    if (addr % 2) {
		ret = page256_write(flash, addr++, 1, 1, buffer++);
		if (ret != qe_ok) {
			goto __exit;
		}
		size--;
	}

	/* set the flash write enable */
	ret = write_enable(flash, qe_true);
	if (ret != qe_ok) {
		goto __exit;
	}

	/* loop write operate. */
	cmd[0] = QE_SPIF_CMD_WORD_PROGRAM;
	while (size >= 2) {
		if (first_write) {
			make_address_byte_array(flash, addr, &cmd[1]);
			cmd_size = flash->address_4byte ? 5 : 4;
			cmd[cmd_size] = *buffer;
			cmd[cmd_size+1] = *(buffer+1);
			first_write = qe_false;
		} else {
            cmd_size = 1;
            cmd[1] = *buffer;
            cmd[2] = *(buffer + 1);			
		}

		n = qe_spi_transfer(flash->spidev, &cmd, QE_NULL,  cmd_size+2);
		if (!n) {
			qe_error("write spi xfer error");
			goto __exit;
		}

		ret = wait_busy(flash);
		if (ret != qe_ok) {
			qe_error("wait busy err:%d", ret);
			goto __exit;
		}

		size -= 2;
        addr += 2;
        buffer += 2;
	}

	/* set the flash write disable for exit AAI mode */
	ret = write_enable(flash, qe_false);

	/* write last one byte data when origin write size is odd */
	if (ret == qe_ok && size == 1) {
		ret = page256_write(flash, addr, 1, 1, buffer);
	}

__exit:
	if (ret != qe_ok) {
		write_enable(flash, qe_false);
	}

	return ret;
}


/**
 * This function enable or disable spiflash 4-Byte address mode
 *
 * @note : The 4-Byte address mode just support for the flash capacity which is large than 16MB(256Mb)
 *
 * @flash : the flash device
 * @en	  : enable if true, else disable
 *
 * @return result
 */
static qe_ret spif_set_4byte_address_mode(qe_spif_dev *flash, qe_bool en)
{
	qe_size n;
	qe_ret ret;
	qe_u8 cmd;

	qe_assert(flash);

	/* write enable */
	ret = write_enable(flash, qe_true);
	if (ret != qe_ok) {
        qe_error("flash write enable err:%d", ret);
		return ret;
	}

	if (en) {
		cmd = QE_SPIF_CMD_ENTER_4B_ADDRESS_MODE;
	} else {
		cmd = QE_SPIF_CMD_EXIT_4B_ADDRESS_MODE;
	}

	n = qe_spi_transfer(flash->spidev, &cmd, QE_NULL, 1);
	if (n > 0) {
		flash->address_4byte = en ? qe_true : qe_false;
		qe_debug("%s 4-Byte address mode", en?"enter":"exit");
	} else {
		ret = qe_err_send;
		qe_error("%s 4-Byte address mode error", en?"enter":"exit");
	}

	return ret;
}

/**
 * @brief Micron flash read UID
 * 
 * @param[in] flash: spif device
 * @param[in] uid: unique id
 * 
 * @note:
 *  - Micron flash unique id is readed using command 0x9F, skip first 3 bytes, length 17 bytes.
 */
static qe_ret micron_read_uid(qe_spif_dev *flash, qe_spif_unique_id *uid)
{
	int n;
	qe_u8 cmd = QE_SPIF_CMD_JEDEC_ID;
	qe_u8 data[20];

	n = qe_spi_send_recv(flash->spidev, &cmd, 1, data, sizeof(data));
	if (n < 0) {
		qe_error("read uid error:%d", n);
		return qe_err_read;
	}

	qe_memcpy(uid->uid, data, 17);
	uid->size = 17;
	return qe_ok;
}

/**
 * @brief Winbond flash read UID
 * 
 * @param[in] flash: spif device
 * @param[in] uid: unique id
 * 
 * @note:
 *  - Winbond flash unique id is readed using command 0x4B, length 12 bytes.
 */
static qe_ret winbond_read_uid(qe_spif_dev *flash, qe_spif_unique_id *uid)
{
	int n;
	qe_u8 cmd = 0x4B;
	qe_u8 data[12];

	n = qe_spi_send_recv(flash->spidev, &cmd, 1, data, sizeof(data));
	if (n < 0) {
		qe_error("read uid error:%d", n);
		return qe_err_read;
	}

	qe_memcpy(uid->uid, data, 12);
	uid->size = 12;
	return qe_ok;
}

static qe_ret spif_read_unique_id(qe_spif_dev *flash, qe_spif_unique_id *uid)
{
	switch (flash->chip.mf_id) {

	case JEDEC_ID_MICRON:
		return micron_read_uid(flash, uid);

	case JEDEC_ID_WINBOND:
		return winbond_read_uid(flash, uid);
	}
	qe_error("%s not support read UID", spif_manufactor_str(flash->chip.mf_id));
	return qe_err_notsupport;
}

/**
 * This function will read data from spiflash.
 *
 * @flash  : the spiflash device
 * @addr   : read start address
 * @size   : read size
 * @buffer : read data buffer
 *
 * @return read length
 */
qe_size qe_spif_read(qe_spif_dev *dev, qe_u32 addr, qe_size size, 
	void *buffer)
{
	qe_size n;
	qe_ret ret;
	qe_u8 cmd[5], cmd_size;

	qe_assert(dev != QE_NULL);
	qe_assert(dev->spidev != QE_NULL);

	/* check flash address bound */
	if ((addr+size) > dev->chip.capacity) {
		qe_error("out of bound, read:0x%x-0x%x capacity:0x%x", 
			addr, addr+size, dev->chip.capacity);
		return 0;
	}

	ret = wait_busy(dev);
	if (ret == qe_ok) {
		cmd[0] = QE_SPIF_CMD_READ_DATA;
		make_address_byte_array(dev, addr, &cmd[1]);
		cmd_size = dev->address_4byte ? 5 : 4;
		n = qe_spi_send_recv(dev->spidev, cmd, cmd_size, buffer, size);
	} else {
		return 0;
	}

	return n;
}


/**
 * This function will write data to spiflash.
 * 
 * @flash  : the spiflash device
 * @addr   : write start address
 * @size   : write size
 * @buffer : write data buffer
 * 
 * @return write length
 */
qe_ret qe_spif_write(qe_spif_dev *flash, qe_u32 addr, qe_size size, const void *buffer)
{
	qe_ret ret = qe_ok;

	//qe_debug("write 0x%x %d %p", addr, size, buffer);

	if (flash->chip.write_mode & QE_SPIF_WM_PAGE_256B) {
		ret = page256_write(flash, addr, size, 256, buffer);
	} else if (flash->chip.write_mode & QE_SPIF_WM_AAI) {
		ret = aai_write(flash, addr, size, buffer);
	}

	return ret;
}

/*
 * This function will erase all flash data on chip
 *
 * @flash : the flash device
 *
 * @return result
 */
qe_ret qe_spif_chip_erase(qe_spif_dev *dev)
{
	qe_size n;
	qe_ret ret;
	qe_u8 cmd[4];

	qe_assert(dev);
	qe_assert(dev->spidev);

    qe_debug("erase chip");

	/* set the flash write enable */
	ret = write_enable(dev, qe_true);
	if (ret != qe_ok) {
		goto __exit;
	}

	cmd[0] = QE_SPIF_CMD_ERASE_CHIP;
    /* dual-buffer write, like AT45DB series flash chip erase operate is different for other flash */
	if (dev->chip.write_mode & QE_SPIF_WM_DUAL_BUFFER) {
		cmd[1] = 0x94;
        cmd[2] = 0x80;
        cmd[3] = 0x9A;
		n = qe_spi_transfer(dev->spidev, &cmd, QE_NULL, 4);
	} else {
		n = qe_spi_transfer(dev->spidev, &cmd, QE_NULL, 1);
	}

	if (!n) {
		qe_error("erase cmd transfer err");
		goto __exit;
	}

	wait_busy(dev);

__exit:
	write_enable(dev, qe_false);
	return ret;
}

/**
 * This function will erase align by erase granularity.
 * 
 * @flash : the spiflash device
 * @addr  : erase start address
 * @size  : erase size
 *
 * @return result
 */
qe_ret qe_spif_erase(qe_spif_dev *dev, qe_u32 addr, qe_size size)
{
	qe_ret ret = qe_ok;
	qe_u8 cmd[5], cmd_size;
	qe_size curr_erase_size, n;
	
	qe_assert(dev);
	qe_assert(dev->spidev);

	/* check flash address bound */
	if ((addr+size) > dev->chip.capacity) {
		qe_error("out of bound, erase:0x%x-0x%x capacity:0x%x", 
			addr, addr+size, dev->chip.capacity);
		return qe_err_range;
	}

	if (size == dev->chip.capacity) {
		return qe_spif_chip_erase(dev);
	}

	curr_erase_size = dev->chip.sector_size;
	// qe_debug("erase size:%d cmd:0x%x", 
    //     curr_erase_size, dev->chip.erase_sector_cmd);

	/* loop erase operate. erase unit is erase granularity */
	while (size) {

		/* write enable */
		ret = write_enable(dev, qe_true);
		if (ret != qe_ok) {
			qe_error("write enable err:%d", ret);
			goto __exit;
		}

		/* do gran erase */
		cmd[0] = dev->chip.erase_sector_cmd;
		make_address_byte_array(dev, addr, &cmd[1]);
		cmd_size = dev->address_4byte ? 5 : 4;
		n = qe_spi_transfer(dev->spidev, cmd, QE_NULL, cmd_size);
		if (n == 0) {
			qe_error("erase cmd transfer err");
			goto __exit;
		}
		//qe_debug("erase 0x%x", addr);

		/* wait busy */
		ret = wait_busy(dev);
		if (ret != qe_ok) {
			qe_error("wait busy");
			goto __exit;
		}

		/* make erase align and calculate next erase address */
		if (addr % curr_erase_size) {
			if (size > curr_erase_size - (addr % curr_erase_size)) {
				size -= curr_erase_size - (addr % curr_erase_size);
				addr += curr_erase_size - (addr % curr_erase_size);
			} else {
				goto __exit;
			}
		} else {
			if (size > curr_erase_size) {
				size -= curr_erase_size;
				addr += curr_erase_size;
			} else {
				goto __exit;
			}
		}
	}

__exit:
	write_enable(dev, qe_false);
	return ret;
}

static qe_size spif_read(qe_dev *dev, qe_offs pos, void *buffer, qe_size size)
{
	qe_size phy_size;
	qe_u32 phy_addr;
	qe_spif_dev *flash;

	flash = (qe_spif_dev *)dev;

	qe_assert(dev);
	qe_assert(flash);
	qe_assert(flash->spidev);

	/* change the block device's logic address to physical address */
	phy_addr = pos * flash->sector_size;
	phy_size = size * flash->sector_size;
	return qe_spif_read(flash, phy_addr, phy_size, buffer);
}

static qe_size spif_write(qe_dev *dev, qe_offs pos, const void *buffer, 
	qe_size size)
{
	qe_size phy_size;
	qe_u32 phy_addr;
	qe_spif_dev *flash;

	flash = (qe_spif_dev *)dev;
	
	qe_assert(dev);
	qe_assert(flash);
	qe_assert(flash->spidev);

	/* change the block device's logic address to physical address */
	phy_addr = pos * flash->sector_size;
	phy_size = size * flash->sector_size;
	return qe_spif_write(flash, phy_addr, phy_size, buffer);
}

static qe_ret block_erase(qe_spif_dev *dev, qe_spif_erase_message *msg)
{
	qe_ret ret = qe_ok;
	qe_u8 cmd[5], cmd_size;
	qe_size curr_erase_size, n;
    qe_u32 addr, size;
	
	qe_assert(dev);
	qe_assert(dev->spidev);

    addr = msg->addr;
    size = msg->size;

	/* check address bound */
	if ((addr+size) > dev->chip.capacity) {
		qe_error("out of range, erase size:0x%x capacity:0x%x", 
			addr+size, dev->chip.capacity);
		return qe_err_range;
	}

	if (size == dev->chip.capacity) {
		return qe_spif_chip_erase(dev);
	}

	curr_erase_size = msg->sector_size;
	//qe_debug("erase size:%d", curr_erase_size);

	/* loop erase operate. erase unit is erase granularity */
	while (size) {

		/* write enable */
		ret = write_enable(dev, qe_true);
		if (ret != qe_ok) {
			qe_error("write enable error:%d", ret);
			goto __exit;
		}

		/* do gran erase */
		cmd[0] = msg->erase_cmd;
		make_address_byte_array(dev, addr, &cmd[1]);
		cmd_size = dev->address_4byte ? 5 : 4;
		n = qe_spi_transfer(dev->spidev, cmd, QE_NULL, cmd_size);
		if (n == 0) {
			qe_error("erase cmd transfer err");
			goto __exit;
		}
		//qe_debug("erase:0x%x", addr);

		/* wait busy */
		ret = wait_busy(dev);
		if (ret != qe_ok) {
			qe_error("wait busy");
			goto __exit;
		}

		/* make erase align and calculate next erase address */
		if (addr % curr_erase_size) {
			if (size > curr_erase_size - (addr % curr_erase_size)) {
				size -= curr_erase_size - (addr % curr_erase_size);
				addr += curr_erase_size - (addr % curr_erase_size);
			} else {
				goto __exit;
			}
		} else {
			if (size > curr_erase_size) {
				size -= curr_erase_size;
				addr += curr_erase_size;
			} else {
				goto __exit;
			}
		}
	}

__exit:
	write_enable(dev, qe_false);
	return ret;
}

static qe_ret spif_ioctl(qe_dev *dev, int cmd, void *args)
{
    qe_spif_dev *flash;
    
    flash = (qe_spif_dev *)dev;

	qe_assert(dev);
	qe_assert(flash);
	qe_assert(flash->spidev);

	qe_debug("ioctl:%d", cmd);

    switch(cmd) {

    case QE_SPIF_IOC_GET_CHIPINFO:
		qe_debug("cmd get chipinfo");
        qe_memcpy(args, &flash->chip, sizeof(qe_spif_chip));
        break;

    case QE_SPIF_IOC_BLOCK_ERASE:
		qe_debug("cmd block erase");
        block_erase(flash, (qe_spif_erase_message *)args);
        break;

	case QE_SPIF_IOC_RESET:
		qe_debug("cmd reset");
		spif_reset(flash);
		break;

	case QE_SPIF_IOC_4BYTE_ENABLE:
		qe_debug("cmd 4byte mode enable:%d", *(qe_bool *)args);
		spif_set_4byte_address_mode(flash, *(qe_bool *)args);
		break;

	case QE_SPIF_IOC_READ_UID:
		spif_read_unique_id(flash, (qe_spif_unique_id *)args);
		break;

    default:
        break;
    }

    return qe_ok;
}

static const qe_dev_ops spif_ops = {
	QE_NULL,
	QE_NULL,
	QE_NULL,
	spif_read,
	spif_write,
	spif_ioctl,
};

qe_spif_dev *qe_spif_find(const char *name)
{
	qe_spif_dev *dev;

	dev = (qe_spif_dev *)qe_dev_find(name);
	if (dev == QE_NULL || dev->parent.type != QE_DEV_BLOCK) {
		qe_warning("spif %s not found", name);
		return QE_NULL;
	}

	return dev;
}

qe_spif_dev *qe_spif_probe(const char *spif_name, const char *spi_name, qe_spif_chip *matchinfo)
{
	int i;
	qe_ret ret;
	qe_bool find = qe_false;
	struct qe_spi_device *spi;
	qe_spif_chip *chip;
	qe_spif_dev *dev;
	char *flash_mf_name = QE_NULL;
	
	qe_assert(spif_name != QE_NULL);
	qe_assert(spi_name != QE_NULL);

	spi = (struct qe_spi_device *)qe_dev_find(spi_name);
	if (!spi) {
		qe_error("spi %s not find", spi_name);
		return QE_NULL;
	}

	dev = qe_malloc(sizeof(qe_spif_dev));
	if (!dev) {
		qe_error("alloc spif error");
		return QE_NULL;
	}

	qe_memset(dev, 0x0, sizeof(qe_spif_dev));
	dev->spidev = spi;

	ret = read_jedec_id(dev);
	if (ret != qe_ok)
		goto __error;

    if (matchinfo) {
		qe_debug("use external match info");
        if ((matchinfo->mf_id       == dev->chip.mf_id) &&
            (matchinfo->mt_id       == dev->chip.mt_id) &&
            (matchinfo->capacity_id == dev->chip.capacity_id)) {
			dev->chip.name             = matchinfo->name;
			dev->chip.capacity         = matchinfo->capacity;
			dev->chip.write_mode       = matchinfo->write_mode;
			dev->chip.sector_size      = matchinfo->sector_size;
			dev->chip.erase_sector_cmd = matchinfo->erase_sector_cmd;
            dev->chip.flag             = matchinfo->flag;
			find = qe_true;
			qe_debug("matched with %s", matchinfo->name);
        }
    }

    if (find) {
        goto __chip_matched;
    }

#if defined(CONFIG_SPI_FLASH_TABLE)
	for (i=0; i<qe_array_size(match_table); i++) {
		chip = &match_table[i];
		if ((chip->mf_id       == dev->chip.mf_id) &&
			(chip->mt_id       == dev->chip.mt_id) &&
			(chip->capacity_id == dev->chip.capacity_id)) {
			dev->chip.name             = chip->name;
			dev->chip.capacity         = chip->capacity;
			dev->chip.write_mode       = chip->write_mode;
			dev->chip.sector_size      = chip->sector_size;
			dev->chip.erase_sector_cmd = chip->erase_sector_cmd;
            dev->chip.flag             = chip->flag;
			find = qe_true;
			qe_debug("matched with %s", matchinfo->name);
			break;
		}
	}
#endif

	if (!find) {
		qe_error("%s not matched", spif_name);
		goto __error;
	}

__chip_matched:

	/* find the manufacturer information */
	for (i=0; i<sizeof(mf_table); i++) {
		if (mf_table[i].id == dev->chip.mf_id) {
			flash_mf_name = (char *)mf_table[i].name;
			qe_debug("find manufacturer %s", flash_mf_name);
			break;
		}
	}

	/* print manufacturer and flash chip name */
	if (flash_mf_name && dev->chip.name) {
		qe_info("%s %s %d %ld bytes (%dMB)",
            flash_mf_name, dev->chip.name,
            dev->chip.sector_size,
            dev->chip.capacity, 
            dev->chip.capacity/(1024*1024));
	}

	/* reset flash device */
    if (dev->chip.flag & FLASH_F_RESET) {
		qe_debug("do reset");
        ret = spif_reset(dev);
        if (ret != qe_ok) {
			qe_error("reset err:%d", ret);
            goto __error;
        }
    }

    if (dev->chip.flag & FLASH_F_4BYTE) {
        /* if the flash is large than 32MB(256Mb) then enter 4-Byte address mode */
        if (dev->chip.capacity > (1L << 24)) {
			qe_debug("set 4byte address");
            ret = spif_set_4byte_address_mode(dev, qe_true);
            if (ret != qe_ok) {
				qe_error("set 4byte address err:%d", ret);
                goto __error;
            }
        } else {
            dev->address_4byte = qe_false;
        }
    }

	dev->num_sectors  = dev->chip.capacity / dev->chip.sector_size;
	dev->sector_size  = dev->chip.sector_size;
	dev->block_size   = dev->chip.sector_size;
	dev->parent.type  = QE_DEV_BLOCK;
	dev->parent.ops   = &spif_ops;
	qe_dev_register(&(dev->parent), spif_name, QE_DEV_F_RDWR|QE_DEV_F_STANDALONE);

	qe_info("probe spif %s by %s success", spif_name, spi_name);

	return dev;

__error:
	if (dev)
		qe_free(dev);
	return QE_NULL;
}



